/*
 * Copyright (C) 2011 Christopher Probst
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of the 'FoxNet Codec' nor the names of its 
 *   contributors may be used to endorse or promote products derived
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.foxnet.codec;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * @author Christopher Probst
 */
public final class SunObjectInstantiator implements ObjectInstantiator {

    // Used to instantiate new objects without a default constructor
    private final Constructor<?> OBJECT_CONS;
    private final Object REFLECTION_FACTORY;
    private final Method CONSTRUCTOR_FOR_SERIALIZATION;
    // Used to cache constructors for their classes
    private final ConcurrentMap<Class<?>, Constructor<?>> constructors =
	    new ConcurrentHashMap<>();

    public SunObjectInstantiator() {
	try {
	    // Tmp cons
	    Constructor<?> tmpObjectCons = null;
	    try {
		// Get object constructor
		tmpObjectCons = Object.class.getConstructor();
	    } catch (NoSuchMethodException e) {
		// Cannot happen!
	    } finally {
		// Finally save the object constructor
		OBJECT_CONS = tmpObjectCons;
	    }

	    // Tmp class
	    Class<?> reflectionFactoryClass;

	    try {
		// Try to find class of reflection factory
		reflectionFactoryClass =
			Class.forName("sun.reflect.ReflectionFactory");
	    } catch (ClassNotFoundException e) {
		// Unsupported platform...
		throw new Error("Unsupported platform. Reason: The class "
			+ "\"sun.reflect.ReflectionFactory\" does not exist.");
	    }

	    try {
		// Try to get method "getReflectionFactory" and invoke it
		REFLECTION_FACTORY =
			reflectionFactoryClass
				.getMethod("getReflectionFactory").invoke(null);
	    } catch (NoSuchMethodException e) {
		// Unsupported platform...
		throw new Error(
			"Unsupported platform. Reason: The class "
				+ "\"sun.reflect.ReflectionFactory\" does not contain the "
				+ "static method \"getReflectionFactory\".");
	    } catch (Exception e) {
		// Unsupported platform...
		throw new Error(
			"Failed to invoke \"sun.reflect."
				+ "ReflectionFactory.getReflectionFactory()\". Reason: "
				+ e.getMessage(), e);
	    }

	    try {
		// Try to get method "newConstructorForSerialization"
		CONSTRUCTOR_FOR_SERIALIZATION =
			reflectionFactoryClass.getMethod(
				"newConstructorForSerialization", Class.class,
				Constructor.class);
	    } catch (NoSuchMethodException e) {
		// Unsupported platform...
		throw new Error(
			"Unsupported platform. Reason: The class "
				+ "\"sun.reflect.ReflectionFactory\" does not contain the "
				+ "method \"newConstructorForSerialization\".");
	    }
	} catch (Exception e) {
	    // Unsupported platform...
	    throw new Error("Failed to create SunObjectInstantiator. "
		    + "Reason:" + e.getMessage(), e);
	}
    }

    /*
     * (non-Javadoc)
     * @see com.foxnet.codec.ObjectInstantiator#newInstance(java.lang.Class)
     */
    @SuppressWarnings("unchecked")
    @Override
    public <T> T newInstance(Class<T> clazz) throws Exception {

	// Try to get cons from map
	Constructor<T> cons = (Constructor<T>) constructors.get(clazz);

	// Create new cons
	if (cons == null) {

	    // At first try to find a default constructor
	    for (Constructor<?> tmpCons : clazz.getDeclaredConstructors()) {
		if (tmpCons.getParameterTypes().length == 0) {
		    tmpCons.setAccessible(true);
		    constructors.put(clazz, cons = (Constructor<T>) tmpCons);
		    // Return a new instance
		    return cons.newInstance();
		}
	    }

	    // No default cons, let's create a special one!
	    constructors.put(
		    clazz,
		    cons =
			    (Constructor<T>) CONSTRUCTOR_FOR_SERIALIZATION
				    .invoke(REFLECTION_FACTORY, clazz,
					    OBJECT_CONS));
	}

	// Return a new instance
	return cons.newInstance();
    }
}
